Day 17 的 Sentinel 解決了「主節點掛了有人接手」,但能寫的還是只有一台主節點,資料也要一台裝得下。
Cluster(叢集)的做法是開好幾台主節點,把資料拆開放,每台只管一部分。
MOVED 自動跳到對的那台)先把 Day 17 的環境關掉(在主機的終端機下):
docker compose -f docker-compose-sentinel.yml down
一樣在 Day 2 的 docker-compose.yml 旁邊,另外寫一份 docker-compose-cluster.yml,六台 Redis:
# Day 18 Cluster:三主三從,六台都開 cluster 模式
name: day18
services:
net: # 只負責開網路跟 port,其他台都借它的網路
image: redis:7-alpine
container_name: day18-net
command: sleep infinity
init: true # 讓 docker stop 不用等 10 秒
ports:
- "7001-7006:7001-7006" # 六台 Redis
redis-7001:
image: redis:7-alpine
container_name: redis-7001
network_mode: service:net
command: redis-server --port 7001 --cluster-enabled yes # 開 cluster 模式
redis-7002:
image: redis:7-alpine
container_name: redis-7002
network_mode: service:net
command: redis-server --port 7002 --cluster-enabled yes
redis-7003:
image: redis:7-alpine
container_name: redis-7003
network_mode: service:net
command: redis-server --port 7003 --cluster-enabled yes
redis-7004:
image: redis:7-alpine
container_name: redis-7004
network_mode: service:net
command: redis-server --port 7004 --cluster-enabled yes
redis-7005:
image: redis:7-alpine
container_name: redis-7005
network_mode: service:net
command: redis-server --port 7005 --cluster-enabled yes
redis-7006:
image: redis:7-alpine
container_name: redis-7006
network_mode: service:net
command: redis-server --port 7006 --cluster-enabled yes
--cluster-enabled yes:開 cluster 模式,沒加的話就是一般的單機 Redisnetwork_mode: service:net:大家都用 127.0.0.1 + 不同 port 找對方,主機的 Java 也連得到光開起來還不是 Cluster,要再用 --cluster create 把六台組起來(在主機的終端機下):
docker compose -f docker-compose-cluster.yml up -d
docker exec redis-7001 redis-cli --cluster create 127.0.0.1:7001 127.0.0.1:7002 127.0.0.1:7003 127.0.0.1:7004 127.0.0.1:7005 127.0.0.1:7006 --cluster-replicas 1 --cluster-yes
--cluster-replicas 1:每個主節點配一台從節點,六台就是三主三從--cluster-yes:不用再問一次,直接照它分好的來
M: 是主節點,7001、7002、7003 各拿一段 slot:0-5460、5461-10922、10923-16383
S: 是從節點,replicates 後面那串是它跟的主節點 ID,對一下上面的 M:,7004 跟 7001、7005 跟 7002、7006 跟 7003[WARNING] 是提醒從節點跟主節點在同一台機器上,正式環境要放在不同機器上主節點掛了,它的從節點會自己升上去,Cluster 自己就會切換,不用另外開 Sentinel。
Cluster 準備了 16384 個格子,叫做 slot(槽),編號 0 到 16383。三台主節點各管一段,剛剛截圖的 0-5460 就是 7001 管的那段。
存一個 key 的時候,Redis 會用 key 的名字算出一個編號,編號落在誰那段,key 就存到誰那台。
進 7001 試試看(在主機的終端機下):
docker exec -it redis-7001 redis-cli -p 7001
CLUSTER KEYSLOT user:1
SET user:1 gary

user:1 算出來是 10778,在 5461-10922 那段,歸 7002 管。7001 不會幫忙轉過去,只回一個 MOVED,告訴你該去找 7002。
exit 出來,加上 -c 再進去一次(在主機的終端機下):
docker exec -it redis-7001 redis-cli -c -p 7001
SET user:1 gary
GET user:1

redis-cli 收到 MOVED 自己跳到 7002 寫進去,前面的提示也從 7001 變成 7002。
一樣在 -c 裡面,一次寫兩個 key:
CLUSTER KEYSLOT a
CLUSTER KEYSLOT b
MSET a 1 b 2
CLUSTER KEYSLOT f
MSET b 1 f 2

a 在 15495(7003),b 在 3300(7001),slot 不一樣,-c 也沒辦法,直接噴 CROSSSLOT。
f 在 3168,跟 b 一樣歸 7001 管,但 MSET b 1 f 2 還是噴錯。一個指令裡的 key,slot 編號要一模一樣,在同一台也不行。除了 MSET,一次 DEL 好幾個 key、Day 7 的 SINTER 也會遇到。
要一起動的 key,可以在名字裡加大括號,Redis 只拿大括號裡面的字來算 slot:
CLUSTER KEYSLOT {user:1}:cart
CLUSTER KEYSLOT {user:1}:view
MSET {user:1}:cart 1 {user:1}:view 2

兩個都只算 user:1,所以都是 10778,同一個 slot,MSET 就過了。
代價是同一個大括號的 key 全部擠在同一台,六台的 Cluster 就只剩一台在做事。
exit 出來,在 application.yml 旁邊新增一份 application-cluster.yml:
# Day 18:啟動時加 --spring.profiles.active=cluster 才會用這份
# 不用六台都寫,連上其中一台就問得到整個 Cluster
spring:
data:
redis:
cluster:
nodes: 127.0.0.1:7001,127.0.0.1:7002,127.0.0.1:7003 # 寫幾台當入口,一台掛了還有別台
Program arguments 改成 --spring.profiles.active=cluster 再啟動。

Java 連上之後會問到每個 slot 在哪台,key 直接送去對的那台。
開另一個終端機,盯著 7001 收到的指令(MONITOR 會印出這台收到的每個指令,grep tag 只留 Day 7 標籤相關的,在主機的終端機下):
docker exec redis-7001 redis-cli -p 7001 MONITOR | grep tag
回到原本的終端機,用 Day 7 的標籤 API 打三筆,再查同時有 3C 跟 sale 的商品(在主機的終端機下):
curl -X POST "localhost:8080/tag/p1?tags=3C"
curl -X POST "localhost:8080/tag/p2?tags=3C,sale"
curl -X POST "localhost:8080/tag/p3?tags=sale"
curl "localhost:8080/tag/search?tags=3C,sale" # ["p2"]
tag:3C 跟 tag:sale 不在同一個 slot,Java 卻沒噴 CROSSSLOT,答案 p2 也是對的。看一下 MONITOR:

前兩行是打標籤的 SADD,第三行才是查詢。
Day 7 查詢用的是 SINTER,Redis 算好交集直接回答案。這次 7001 收到的卻是 SMEMBERS(全部撈出來)。
兩個 key 不在同一個 slot,Spring 就換個做法:
tag:3C 底下全部的商品(p1、p2)tag:sale 底下全部的商品(p2、p3)同一個查詢直接用 redis-cli 下,就是剛剛的 CROSSSLOT(在主機的終端機下):
docker exec redis-7001 redis-cli -c -p 7001 SINTER tag:3C tag:sale

不管單機還是 Cluster,平常下指令都是一個一個送:送出去、等回覆、再送下一個。一次要寫一萬筆,就要來回一萬次。之前灌資料有用到的 --pipe 就是在解決這件事,明天繼續來看 Pipeline 跟 MULTI![]()